// Copyright (c) Facebook, Inc. and its affiliates.
//
// This source code is licensed under the MIT license found in the
// LICENSE file in the root directory of this source tree.

use alloc::vec::Vec;

use math::{FieldElement, StarkField};

use crate::{errors::RandomCoinError, ElementHasher, Hasher};

mod default;
pub use default::DefaultRandomCoin;

// RANDOM COIN TRAIT
// ================================================================================================

/// Pseudo-random element generator for finite fields.
///
/// A random coin can be used to draw elements uniformly at random from the specified base field
/// or from any extension of the base field.
///
/// Internally we use a cryptographic hash function (which is specified via the `Hasher` associated
/// type), to draw elements from the field.
pub trait RandomCoin: Sync {
    /// Base field for random elements which can be generated by this random coin.
    type BaseField: StarkField;

    /// Hash function which is used by the random coin to generate random field elements.
    type Hasher: ElementHasher<BaseField = Self::BaseField>;

    // REQUIRED METHODS
    // --------------------------------------------------------------------------------------------

    /// Returns a new random coin instantiated with the provided `seed`.
    fn new(seed: &[Self::BaseField]) -> Self;

    /// Reseeds the coin with the specified data by setting the new seed to hash(`seed` || `data`).
    fn reseed(&mut self, data: <Self::Hasher as Hasher>::Digest);

    /// Computes hash(`seed` || `value`) and returns the number of leading zeros in the resulting
    /// value if it is interpreted as an integer in big-endian byte order.
    fn check_leading_zeros(&self, value: u64) -> u32;

    /// Returns the next pseudo-random field element.
    ///
    /// # Errors
    /// Returns an error if a valid field element could not be generated after 1000 calls to the
    /// PRNG.
    fn draw<E: FieldElement<BaseField = Self::BaseField>>(&mut self) -> Result<E, RandomCoinError>;

    /// Returns a vector of integers selected from the range [0, domain_size) after it reseeds
    /// the coin with a nonce.
    ///
    /// # Errors
    /// Returns an error if the specified number of integers could not be generated after 1000
    /// calls to the PRNG.
    ///
    /// # Panics
    /// Panics if:
    /// - `domain_size` is not a power of two.
    /// - `num_values` is greater than or equal to `domain_size`.
    fn draw_integers(
        &mut self,
        num_values: usize,
        domain_size: usize,
        nonce: u64,
    ) -> Result<Vec<usize>, RandomCoinError>;
}
